"""
This module contains all necessary functions to administrate 
the certificate authority. 

Todo:

* create system call handler 
* integrate better logging facility 
* add input validation
* add status to each certificate in the listing (valid/revoked)  
"""

"""
    This file is part of project webCA

    webCA is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    webCA is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with webCA. If not, see <http://www.gnu.org/licenses/>.
"""

import logging

from pylons import request, response, session, tmpl_context as c
from pylons.controllers.util import abort, redirect_to

from webca.lib.base import BaseController, render

# needed for link_to()
import webca.lib.helpers as h
# needed to access app_globals.py
from pylons import config 
# needed for file and folder functions
import os, subprocess, sys, webca.controllers.tools as tools 

log = logging.getLogger(__name__)

class AdminController(BaseController):
    requires_auth = True
    
    def boot_strap(self):
        """module to initialize the CA, it creates the folder structure, 
        creates and fills the OpenSSL serial file, database. Also 
        it creates a self signed Root Certificate        
        
        Todo:
        * output folder/file creation to view
        * needs more/better checking 
        """
        # set title, page name and footer containing all the links  
        c.title = 'CA Management'
        c.page_name = 'CA Management'
        c.footer =  h.link_to('CA Management', h.url (controller='admin', action='boot_strap')) + ' | ' + \
                    h.link_to('Request Management', h.url(controller='admin', action='sign_req')) + ' | ' + \
                    h.link_to('Certificate Management', h.url(controller='admin', action='certs')) + ' | ' + \
                    h.link_to('Logout', h.url(controller='login', action='logout')) 
                       
        # filling the form fields with data from the openssl configuration 
        # for now these fields are marked read only within the template boot_strap.mako (view)  
        c.ca_base_dir = config['pylons.app_globals'].dir
        c.ca_cert_dir = config['pylons.app_globals'].certs
        c.ca_crl_dir = config['pylons.app_globals'].crl_dir
        c.ca_database = config['pylons.app_globals'].database
        c.ca_new_certs_dir = config['pylons.app_globals'].new_certs_dir
        c.ca_key_dir = config['pylons.app_globals'].key_dir
        c.ca_csr_dir = config['pylons.app_globals'].csr_dir
        c.ca_serial = config['pylons.app_globals'].serial 
        c.ca_crlnumber = config['pylons.app_globals'].crlnumber 
        
        # the administrator has hit the "Boot Strap" button 
        if request.params.get('submit') == 'boot_strap':
            # is there already a root certificate, has the ca already been initialised? 
            if os.path.exists(config['pylons.app_globals'].key_dir + '/rootCA.key') and os.path.exists(config['pylons.app_globals'].certs + '/rootCA.crt'):
                c.stdout_value = 'CA has already been boot straped, at least there is a root cert and a root key'
                if config['ca_debug']: print 'CA has already been boot straped, at least there is a root cert and a root key'
            # no, let's go ahead
            else:
                # provide some debugging information to the paster console 
                if config['ca_debug']: print 'boot straping CA...'
                
                # create the folder structure 
                tools.make_folder(config['pylons.app_globals'].dir)
                tools.make_folder(config['pylons.app_globals'].certs)
                tools.make_folder(config['pylons.app_globals'].crl_dir)
                tools.make_file(config['pylons.app_globals'].database)
                tools.make_folder(config['pylons.app_globals'].new_certs_dir)
                tools.make_folder(config['pylons.app_globals'].key_dir)
                tools.make_folder(config['pylons.app_globals'].csr_dir)
                
                # create the serial file
                tools.make_file(config['pylons.app_globals'].serial)
                # create the crlnumber file 
                tools.make_file(config['pylons.app_globals'].crlnumber)
                
                # write the first serial number 
                f = open(config['pylons.app_globals'].crlnumber, 'w')
                f.write('01\n')
                f.close()    
                
                # write the first serial number 
                f = open(config['pylons.app_globals'].serial, 'w')
                f.write('01\n')
                f.close()  
                
                # building the subject line
                subject = tools.build_subject(
                                              request.params.get('ca_country'), 
                                              request.params.get('ca_state'), 
                                              request.params.get('ca_location'), 
                                              request.params.get('ca_organisation'), 
                                              request.params.get('ca_organisation_unit'),
                                              request.params.get('ca_common_name'),
                                              request.params.get('ca_email')
                                              )
                
                # putting the openssl command together
                cmd = [ 
                        config['pylons.app_globals'].openssl_bin, 
                        'req', 
                        '-config', config['openssl_config'], 
                        '-new', 
                        '-newkey', 'rsa:2048', 
                        '-x509', 
                        '-extensions', request.params.get('ca_ext_section'), 
                        '-keyout', config['pylons.app_globals'].key_dir + '/rootCA.key', 
                        '-out', config['pylons.app_globals'].certs + '/rootCA.crt', 
                        '-batch', 
                        '-passout', 
                        'pass:' + config['ca_pass'], 
                        '-subj', subject, 
                        '-days', '365'
                    ]
                
                # try to execute our command 
                try:
                    proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                    c.stdout_value = proc.communicate()[1]
                                             
                    # there seems to have an error occured                          
                    if proc.returncode < 0:
                        message = 'Child was terminated b signal', proc.returncode
                        print >> sys.stderr, message 
                    # okay everything went fine 
                    else:
                        message = 'Child returned', proc.returncode
                        print >> sys.stderr, message
                except OSError, e:
                        message = 'Execution failed:', e
                        print >> sys.stderr, message
        
        # the administrator wants to see the root certificate 
        elif request.params.get('submit') == 'print_ca':
            # provide some debug information
            if config['ca_debug']: print 'requesting print_ca'
            
            # build the path 
            root_cert = config['pylons.app_globals'].certs + '/rootCA.crt'
            
            # is there really such a root certificate?
            if os.path.exists(root_cert):
                 cmd = [
                        config['pylons.app_globals'].openssl_bin, 
                        'x509', 
                        '-noout', 
                        '-text', 
                        '-in', root_cert
                        ]
                 
                 # debug info
                 if config['ca_debug']: print repr(cmd)
                 
                 # lets execute our command 
                 try:
                    # execute!
                    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                    # communicate to the user 
                    c.stdout_value = proc.communicate()[0]
                                            
                    # there was an error
                    if proc.returncode < 0:
                        message = 'Child was terminated b signal', proc.returncode
                        print >> sys.stderr, message
                    # okay seems to have worked...
                    else:
                        message = 'Child returned', proc.returncode
                        print >> sys.stderr, message
                 except OSError, e:
                     message = 'Execution failed:', e
                     print >> sys.stderr, message
            # no there is no root certificate, init first 
            else:
                c.stdout_value = 'root cert ist not there, please boot strap first'

        # the administrator wants to download the root certificate 
        elif request.params.get('submit') == 'get_ca_cert':
            # debug information
            if config['ca_debug']: print 'requesting download root ca'
            
            # build the filename for the root certificate 
            filename = config['pylons.app_globals'].certs + '/rootCA.crt'
        
            # is there really such a file?
            if os.path.exists(filename):
                if config['ca_debug']: print 'get_ca_cert(): file exists'
                
                # create a file handle 
                fh = open(filename, 'r')
                
                # read the file 
                data = fh.read()
                
                # ...and give it back to the user 
                response.content_type = 'application/octet-stream'
                response.headers['content-length'] = len(data)
                response.headers['content-disposition'] = 'attachment; filename="rootCA.crt"'
                response.content = data
                
                return response
            # opps there is no such file 
            else:
                # give the user some information 
                c.stdout_value = 'cert does not yet exists, please boot strap first'
        # the administrator wants to see the openssl configuration file 
        elif request.params.get('submit') == 'print_ca_config':
            # open the openssl configuration in read mode 
            fh = open(config['openssl_config'], 'r')
            # ...and give it back 
            c.stdout_value = fh.read()
                    
        return render('boot_strap.mako')
    
    def certs(self):
        """This module handles the CA's certificates, it is responsible for 
        certificate printing and revocation, also the OpenSSL database can 
        be printed.
        
        Todo:
        
        * provide better checks 
        """
        # set title, page name and footer containing all the links
        c.title = 'Certificate Management'
        c.page_name = 'Certificate Management'
        c.footer =  h.link_to('CA Management', h.url (controller='admin', action='boot_strap')) + ' | ' + \
                    h.link_to('Request Management', h.url(controller='admin', action='sign_req')) + ' | ' + \
                    h.link_to('Certificate Management', h.url(controller='admin', action='certs')) + ' | ' + \
                    h.link_to('Logout', h.url(controller='login', action='logout')) 
        
        cert_list = []
        files = []
        
        # is there really a directory containing the certificates?
        if os.path.exists(config['pylons.app_globals'].certs):
            # yes, give me a list containg all the files 
            files = os.listdir(config['pylons.app_globals'].certs)
        
        # for every file in the file array, get the filename and add it to the 
        # certificate list 
        for file in files:
            cert_list.append(file.__str__())
        
        # pass this information to the template 
        c.cert_list = cert_list
                       
        # the administrator wants to see a certificate 
        if request.params.get('submit') == 'print_cert':
            temp = ''
                
            # build the list together 
            for i, cert in enumerate(cert_list):
                # is there a check on the checkbox?
                if request.params.get(repr(i)) is not None:
                    # okay, let's put our command together 
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'x509', 
                           '-text', 
                           '-noout', 
                           '-in', config['pylons.app_globals'].certs + '/' + cert
                           ]
                    # some debug information
                    if config['ca_debug']: print cmd
                    
                    # try to execute the command 
                    try: 
                        # execute 
                        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                        # concatenate the output 
                        temp += proc.communicate()[0]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
            # pass the certificates text representation to the template  
            c.stdout_value = temp
        
        # the administrator wants to revoke a certificate 
        elif request.params.get('submit') == 'revoke_cert':
            temp = ''
                
            # for every certificate within the certificate list 
            for i, cert in enumerate(cert_list):
                # is there a check on the checkbox?
                if request.params.get(repr(i)) is not None:
                    # put the command together 
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'ca',
                           '-revoke', config['pylons.app_globals'].certs + '/' + cert, 
                           '-config', config['openssl_config'],  
                           '-batch', 
                           '-key', config['ca_pass']
                           ]
                    # debuf information 
                    if config['ca_debug']: print cmd
                    
                    # execute the code 
                    try: 
                        proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                        temp += proc.communicate()[1]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
            
            # pass ouput back to the template 
            c.stdout_value = temp
        
        # the administrator wants to see the openssl database 
        elif request.params.get('submit') == 'print_cert_db':
            # open it 
            fh = open(config['pylons.app_globals'].database)
            # give it back to the template 
            c.stdout_value = fh.read()
    
        return render('certs.mako')

    def sign_req(self):
        """This module is responsible for printing, signing and deleting 
        of certificate signing requests. 
                
        Todo:
        
        * ...
        """
        # set title, page name and footer containing all the links
        c.title = 'Request Management'
        c.page_name = 'Request Management'
        c.footer =  h.link_to('CA Management', h.url (controller='admin', action='boot_strap')) + ' | ' + \
                    h.link_to('Request Management', h.url(controller='admin', action='sign_req')) + ' | ' + \
                    h.link_to('Certificate Management', h.url(controller='admin', action='certs')) + ' | ' + \
                    h.link_to('Logout', h.url(controller='login', action='logout')) 
        
        csr_list = []
        files = []
        
        # is there really a dir? 
        if os.path.exists(config['pylons.app_globals'].csr_dir):
            # yes, give me a list of all filenames in it 
            files = os.listdir(config['pylons.app_globals'].csr_dir)
        
        for file in files:
            csr_list.append(file.__str__())
        
        c.csr_list = csr_list
        
        # the administrator wants to see a request 
        if request.params.get('submit') == 'print_req':
            temp = ''
                
            # for every checked request in the list 
            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    # build the command together 
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'req', 
                           '-text', 
                           '-noout', 
                           '-in', config['pylons.app_globals'].csr_dir + '/' + csr
                           ]
                    
                    # provide debug info 
                    if config['ca_debug']: print cmd
                    
                    # execute the command                                                   
                    try: 
                        # execute 
                        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
                        # concatenate the output 
                        temp += proc.communicate()[0]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
            # ...and give it back to the user         
            c.stdout_value = temp
            
        # the administrator wants to sign a request              
        elif request.params.get('submit') == 'sign_req':
            # for every checked item on the template 
            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    # put the command together 
                    cmd = [
                           config['pylons.app_globals'].openssl_bin, 
                           'ca', 
                           '-config', config['openssl_config'], 
                           '-out', config['pylons.app_globals'].certs + '/' + csr.rstrip('.csr') + '.crt', 
                           '-in', config['pylons.app_globals'].csr_dir + '/' + csr, 
                           '-batch', 
                           '-passin', 'pass:' + config['ca_pass']
                           ]
                    
                    # only for debugging 
                    if config['ca_debug']: print cmd
                    
                    # execute 
                    try:
                        proc = subprocess.Popen(cmd, stderr=subprocess.PIPE)
                        c.stdout_value = proc.communicate()[1]
                                                                           
                        if proc.returncode < 0:
                            message = 'Child was terminated b signal', proc.returncode
                            print >> sys.stderr, message 
                        else:
                            message = 'Child returned', proc.returncode
                            print 'removing', config['pylons.app_globals'].csr_dir + '/' + csr 
                            os.remove(config['pylons.app_globals'].csr_dir + '/' + csr)
                            print >> sys.stderr, message
                    except OSError, e:
                            message = 'Execution failed:', e
                            print >> sys.stderr, message
                            
        # the administrator wants to delete a request and the responding key  
        elif request.params.get('submit') == 'del_req':
            temp = ''
            
            # delete every checked item on the list 
            for i, csr in enumerate(csr_list):
                if request.params.get(repr(i)) is not None:
                    try:
                        # delete csr 
                        os.remove(config['pylons.app_globals'].csr_dir + '/' + csr)
                        temp += 'deleted:' + repr(csr) + '\n'
                    except OSError, e:
                        temp += 'could not delete: ' + repr(csr) + ' ' + repr(e)
                    
                    try:
                        # delete keys
                        os.remove(config['pylons.app_globals'].key_dir + '/' + csr.rstrip('.csr') + '.key')
                        temp += 'deleted:' + repr(csr.rstrip('.csr') + '.key') + '\n'
                    except OSError, e:
                        temp += 'could not delete: ' + repr(csr) + ' ' + repr(e)

            c.stdout_value = temp

            if config['ca_debug']: print temp
                                                        
        return render('sign_req.mako')
